/*-------------------------------------------------------------------------
 *
 * opclasscmds.c
 *
 *	  操作类（和操作族）操作命令的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/opclasscmds.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <limits.h>

#include "access/genam.h"
#include "access/hash.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/alter.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"

static void fc_AlterOpFamilyAdd(AlterOpFamilyStmt *fc_stmt,
							 Oid fc_amoid, Oid fc_opfamilyoid,
							 int fc_maxOpNumber, int fc_maxProcNumber,
							 int fc_opclassOptsProcNumber, List *fc_items);
static void fc_AlterOpFamilyDrop(AlterOpFamilyStmt *fc_stmt,
							  Oid fc_amoid, Oid fc_opfamilyoid,
							  int fc_maxOpNumber, int fc_maxProcNumber,
							  List *fc_items);
static void fc_processTypesSpec(List *fc_args, Oid *fc_lefttype, Oid *fc_righttype);
static void fc_assignOperTypes(OpFamilyMember *fc_member, Oid fc_amoid, Oid fc_typeoid);
static void fc_assignProcTypes(OpFamilyMember *fc_member, Oid fc_amoid, Oid fc_typeoid,
							int fc_opclassOptsProcNum);
static void fc_addFamilyMember(List **fc_list, OpFamilyMember *fc_member);
static void fc_storeOperators(List *fc_opfamilyname, Oid fc_amoid, Oid fc_opfamilyoid,
						   List *fc_operators, bool fc_isAdd);
static void fc_storeProcedures(List *fc_opfamilyname, Oid fc_amoid, Oid fc_opfamilyoid,
							List *fc_procedures, bool fc_isAdd);
static bool fc_typeDepNeeded(Oid fc_typid, OpFamilyMember *fc_member);
static void fc_dropOperators(List *fc_opfamilyname, Oid fc_amoid, Oid fc_opfamilyoid,
						  List *fc_operators);
static void fc_dropProcedures(List *fc_opfamilyname, Oid fc_amoid, Oid fc_opfamilyoid,
						   List *fc_procedures);

/*
 * OpFamilyCacheLookup
 *		通过名称查找现有的操作符族。
 *
 * 返回一个 syscache 元组引用，如果未找到，则返回 NULL。
 */
static HeapTuple fc_OpFamilyCacheLookup(Oid fc_amID, List *fc_opfamilyname, bool fc_missing_ok)
{
	char	   *fc_schemaname;
	char	   *fc_opfname;
	HeapTuple	fc_htup;

	/* 解构名称列表 */
	DeconstructQualifiedName(fc_opfamilyname, &fc_schemaname, &fc_opfname);

	if (fc_schemaname)
	{
		/* 仅在特定模式中查找 */
		Oid			fc_namespaceId;

		fc_namespaceId = LookupExplicitNamespace(fc_schemaname, fc_missing_ok);
		if (!OidIsValid(fc_namespaceId))
			fc_htup = NULL;
		else
			fc_htup = SearchSysCache3(OPFAMILYAMNAMENSP,
								   ObjectIdGetDatum(fc_amID),
								   PointerGetDatum(fc_opfname),
								   ObjectIdGetDatum(fc_namespaceId));
	}
	else
	{
		/* 未限定的操作符族名称，因此搜索搜索路径 */
		Oid			fc_opfID = OpfamilynameGetOpfid(fc_amID, fc_opfname);

		if (!OidIsValid(fc_opfID))
			fc_htup = NULL;
		else
			fc_htup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(fc_opfID));
	}

	if (!HeapTupleIsValid(fc_htup) && !fc_missing_ok)
	{
		HeapTuple	fc_amtup;

		fc_amtup = SearchSysCache1(AMOID, ObjectIdGetDatum(fc_amID));
		if (!HeapTupleIsValid(fc_amtup))
			elog(ERROR, "cache lookup failed for access method %u", fc_amID);
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("operator family \"%s\" does not exist for access method \"%s\"",
						NameListToString(fc_opfamilyname),
						NameStr(((Form_pg_am) GETSTRUCT(fc_amtup))->amname))));
	}

	return fc_htup;
}

/*
 * get_opfamily_oid
 *	  通过可能限定的名称查找操作符族 OID
 *
 * 如果未找到，若 missing_ok 则返回 InvalidOid，否则抛出错误。
 */
Oid get_opfamily_oid(Oid fc_amID, List *fc_opfamilyname, bool fc_missing_ok)
{
	HeapTuple	fc_htup;
	Form_pg_opfamily fc_opfamform;
	Oid			fc_opfID;

	fc_htup = fc_OpFamilyCacheLookup(fc_amID, fc_opfamilyname, fc_missing_ok);
	if (!HeapTupleIsValid(fc_htup))
		return InvalidOid;
	fc_opfamform = (Form_pg_opfamily) GETSTRUCT(fc_htup);
	fc_opfID = fc_opfamform->oid;
	ReleaseSysCache(fc_htup);

	return fc_opfID;
}

/*
 * OpClassCacheLookup
 *		通过名称查找现有的操作符类。
 *
 * 返回一个 syscache 元组引用，如果未找到，则返回 NULL。
 */
static HeapTuple fc_OpClassCacheLookup(Oid fc_amID, List *fc_opclassname, bool fc_missing_ok)
{
	char	   *fc_schemaname;
	char	   *fc_opcname;
	HeapTuple	fc_htup;

	/* 解构名称列表 */
	DeconstructQualifiedName(fc_opclassname, &fc_schemaname, &fc_opcname);

	if (fc_schemaname)
	{
		/* 仅在特定模式中查找 */
		Oid			fc_namespaceId;

		fc_namespaceId = LookupExplicitNamespace(fc_schemaname, fc_missing_ok);
		if (!OidIsValid(fc_namespaceId))
			fc_htup = NULL;
		else
			fc_htup = SearchSysCache3(CLAAMNAMENSP,
								   ObjectIdGetDatum(fc_amID),
								   PointerGetDatum(fc_opcname),
								   ObjectIdGetDatum(fc_namespaceId));
	}
	else
	{
		/* 未限定的 opclass 名称，因此搜索搜索路径 */
		Oid			fc_opcID = OpclassnameGetOpcid(fc_amID, fc_opcname);

		if (!OidIsValid(fc_opcID))
			fc_htup = NULL;
		else
			fc_htup = SearchSysCache1(CLAOID, ObjectIdGetDatum(fc_opcID));
	}

	if (!HeapTupleIsValid(fc_htup) && !fc_missing_ok)
	{
		HeapTuple	fc_amtup;

		fc_amtup = SearchSysCache1(AMOID, ObjectIdGetDatum(fc_amID));
		if (!HeapTupleIsValid(fc_amtup))
			elog(ERROR, "cache lookup failed for access method %u", fc_amID);
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("operator class \"%s\" does not exist for access method \"%s\"",
						NameListToString(fc_opclassname),
						NameStr(((Form_pg_am) GETSTRUCT(fc_amtup))->amname))));
	}

	return fc_htup;
}

/*
 * get_opclass_oid
 *	  通过可能限定的名称查找操作符类 OID
 *
 * 如果未找到，若 missing_ok 则返回 InvalidOid，否则抛出错误。
 */
Oid get_opclass_oid(Oid fc_amID, List *fc_opclassname, bool fc_missing_ok)
{
	HeapTuple	fc_htup;
	Form_pg_opclass fc_opcform;
	Oid			fc_opcID;

	fc_htup = fc_OpClassCacheLookup(fc_amID, fc_opclassname, fc_missing_ok);
	if (!HeapTupleIsValid(fc_htup))
		return InvalidOid;
	fc_opcform = (Form_pg_opclass) GETSTRUCT(fc_htup);
	fc_opcID = fc_opcform->oid;
	ReleaseSysCache(fc_htup);

	return fc_opcID;
}

/*
 * CreateOpFamily
 *		内部例程，为新的操作符族创建目录条目。
 *
 * 调用者必须已经执行权限检查等。
 */
static ObjectAddress fc_CreateOpFamily(CreateOpFamilyStmt *fc_stmt, const char *fc_opfname,
			   Oid fc_namespaceoid, Oid fc_amoid)
{
	Oid			fc_opfamilyoid;
	Relation	fc_rel;
	HeapTuple	fc_tup;
	Datum		fc_values[Natts_pg_opfamily];
	bool		fc_nulls[Natts_pg_opfamily];
	NameData	fc_opfName;
	ObjectAddress fc_myself,
				fc_referenced;

	fc_rel = table_open(OperatorFamilyRelationId, RowExclusiveLock);

	/*
	 * 确保没有现有的同名操作符族（这只是为了提供比“重复键”更友好的错误消息）。
	 */
	if (SearchSysCacheExists3(OPFAMILYAMNAMENSP,
							  ObjectIdGetDatum(fc_amoid),
							  CStringGetDatum(fc_opfname),
							  ObjectIdGetDatum(fc_namespaceoid)))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("operator family \"%s\" for access method \"%s\" already exists",
						fc_opfname, fc_stmt->amname)));

	/*
	 * 好吧，让我们创建 pg_opfamily 条目。
	 */
	memset(fc_values, 0, sizeof(fc_values));
	memset(fc_nulls, false, sizeof(fc_nulls));

	fc_opfamilyoid = GetNewOidWithIndex(fc_rel, OpfamilyOidIndexId,
									 Anum_pg_opfamily_oid);
	fc_values[Anum_pg_opfamily_oid - 1] = ObjectIdGetDatum(fc_opfamilyoid);
	fc_values[Anum_pg_opfamily_opfmethod - 1] = ObjectIdGetDatum(fc_amoid);
	namestrcpy(&fc_opfName, fc_opfname);
	fc_values[Anum_pg_opfamily_opfname - 1] = NameGetDatum(&fc_opfName);
	fc_values[Anum_pg_opfamily_opfnamespace - 1] = ObjectIdGetDatum(fc_namespaceoid);
	fc_values[Anum_pg_opfamily_opfowner - 1] = ObjectIdGetDatum(GetUserId());

	fc_tup = heap_form_tuple(fc_rel->rd_att, fc_values, fc_nulls);

	CatalogTupleInsert(fc_rel, fc_tup);

	heap_freetuple(fc_tup);

	/*
	 * 为操作符族本身创建依赖关系。
	 */
	fc_myself.classId = OperatorFamilyRelationId;
	fc_myself.objectId = fc_opfamilyoid;
	fc_myself.objectSubId = 0;

	/* 对访问方法的依赖 */
	fc_referenced.classId = AccessMethodRelationId;
	fc_referenced.objectId = fc_amoid;
	fc_referenced.objectSubId = 0;
	recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_AUTO);

	/* 对命名空间的依赖 */
	fc_referenced.classId = NamespaceRelationId;
	fc_referenced.objectId = fc_namespaceoid;
	fc_referenced.objectSubId = 0;
	recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_NORMAL);

	/* 对所有者的依赖 */
	recordDependencyOnOwner(OperatorFamilyRelationId, fc_opfamilyoid, GetUserId());

	/* 对扩展的依赖 */
	recordDependencyOnCurrentExtension(&fc_myself, false);

	/* 向可能感兴趣的事件触发器报告新的操作符族 */
	EventTriggerCollectSimpleCommand(fc_myself, InvalidObjectAddress,
									 (Node *) fc_stmt);

	/* 新操作符族的创建后钩子 */
	InvokeObjectPostCreateHook(OperatorFamilyRelationId, fc_opfamilyoid, 0);

	table_close(fc_rel, RowExclusiveLock);

	return fc_myself;
}

/*
 * DefineOpClass
 *		定义一个新的索引操作符类。
 */
ObjectAddress DefineOpClass(CreateOpClassStmt *fc_stmt)
{
	char	   *fc_opcname;		/* 我们正在创建的操作符类的名称 */
	Oid			fc_amoid,			/* 我们的 AM 的 oid */
				fc_typeoid,		/* 可索引数据类型的 oid */
				fc_storageoid,		/* 存储数据类型的 oid（如果有） */
				fc_namespaceoid,	/* 创建操作符类的命名空间 */
				fc_opfamilyoid,	/* 包含操作符族的 oid */
				fc_opclassoid;		/* 我们创建的操作符类的 oid */
	int			fc_maxOpNumber,	/* amstrategies 值 */
				fc_optsProcNumber, /* amoptsprocnum 值 */
				fc_maxProcNumber;	/* amsupport 值 */
	bool		fc_amstorage;		/* amstorage 标志 */
	List	   *fc_operators;		/* 操作符的 OpFamilyMember 列表 */
	List	   *fc_procedures;		/* 支持过程的 OpFamilyMember 列表 */
	ListCell   *fc_l;
	Relation	fc_rel;
	HeapTuple	fc_tup;
	Form_pg_am	fc_amform;
	IndexAmRoutine *fc_amroutine;
	Datum		fc_values[Natts_pg_opclass];
	bool		fc_nulls[Natts_pg_opclass];
	AclResult	fc_aclresult;
	NameData	fc_opcName;
	ObjectAddress fc_myself,
				fc_referenced;

	/* 将名称列表转换为一个名称和命名空间 */
	fc_namespaceoid = QualifiedNameGetCreationNamespace(fc_stmt->opclassname,
													 &fc_opcname);

	/* 检查我们在目标命名空间中是否有创建权限 */
	fc_aclresult = pg_namespace_aclcheck(fc_namespaceoid, GetUserId(), ACL_CREATE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
					   get_namespace_name(fc_namespaceoid));

	/* 获取关于访问方法的必要信息 */
	fc_tup = SearchSysCache1(AMNAME, CStringGetDatum(fc_stmt->amname));
	if (!HeapTupleIsValid(fc_tup))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("access method \"%s\" does not exist",
						fc_stmt->amname)));

	fc_amform = (Form_pg_am) GETSTRUCT(fc_tup);
	fc_amoid = fc_amform->oid;
	fc_amroutine = GetIndexAmRoutineByAmId(fc_amoid, false);
	ReleaseSysCache(fc_tup);

	fc_maxOpNumber = fc_amroutine->amstrategies;
	/* 如果 amstrategies 为零，仅确保操作数适合 int16 */
	if (fc_maxOpNumber <= 0)
		fc_maxOpNumber = SHRT_MAX;
	fc_maxProcNumber = fc_amroutine->amsupport;
	fc_optsProcNumber = fc_amroutine->amoptsprocnum;
	fc_amstorage = fc_amroutine->amstorage;

	/* XXX 我们是否应该对 AM 进行任何特权检查？ */

	/*
	 * CREATE OPERATOR CLASS 的适当权限问题很有趣。
	 * 创建一个操作类相当于授予涉及的函数的公共执行访问权限，因为索引机制通常在使用函数之前不会检查访问权限。
	 * 因此，最低期望是调用者拥有带授权选项的执行权限。由于我们没有办法在撤销授权选项后使操作类消失，我们选择要求拥有函数的所有权。
	 * 对于数据类型应该要求什么权限也并不完全清楚，但所有权似乎是一个安全的选择。
	 *
	 * 目前，我们要求超级用户权限来创建操作类。这似乎是必要的，因为我们没有办法验证所提供的一组操作符和函数是否与 AM 的期望一致。
	 * 如果有可能在不解决停机问题的情况下提供这样的检查，那将是件好事 :-(
	 *
	 * XXX 如果您删除此测试，请重新启用下面的 NOT_USED 代码段。
	 */
	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to create an operator class")));

	/* 查找数据类型 */
	fc_typeoid = typenameTypeId(NULL, fc_stmt->datatype);

#ifdef NOT_USED
	/* XXX 鉴于上面的超级用户检查，这个是多余的 */
	/* 检查我们是否拥有数据类型的所有权 */
	if (!pg_type_ownercheck(fc_typeoid, GetUserId()))
		aclcheck_error_type(ACLCHECK_NOT_OWNER, fc_typeoid);
#endif

	/*
	 * 查找包含的操作符族，或者如果未指定 FAMILY 选项且没有匹配项，就创建一个。
	 */
	if (fc_stmt->opfamilyname)
	{
		fc_opfamilyoid = get_opfamily_oid(fc_amoid, fc_stmt->opfamilyname, false);
	}
	else
	{
		/* 查找同名和同命名空间的现有族 */
		fc_tup = SearchSysCache3(OPFAMILYAMNAMENSP,
							  ObjectIdGetDatum(fc_amoid),
							  PointerGetDatum(fc_opcname),
							  ObjectIdGetDatum(fc_namespaceoid));
		if (HeapTupleIsValid(fc_tup))
		{
			fc_opfamilyoid = ((Form_pg_opfamily) GETSTRUCT(fc_tup))->oid;

			/*
			 * XXX 鉴于上面的超级用户检查，这里不需要所有权检查
			 */
			ReleaseSysCache(fc_tup);
		}
		else
		{
			CreateOpFamilyStmt *fc_opfstmt;
			ObjectAddress fc_tmpAddr;

			fc_opfstmt = makeNode(CreateOpFamilyStmt);
			fc_opfstmt->opfamilyname = fc_stmt->opclassname;
			fc_opfstmt->amname = fc_stmt->amname;

			/*
			 * 创建它……再次无需更多权限……
			 */
			fc_tmpAddr = fc_CreateOpFamily(fc_opfstmt, fc_opcname, fc_namespaceoid, fc_amoid);
			fc_opfamilyoid = fc_tmpAddr.objectId;
		}
	}

	fc_operators = NIL;
	fc_procedures = NIL;

	/* 存储数据类型是可选的 */
	fc_storageoid = InvalidOid;

	/*
	 * 扫描 "items" 列表以获取更多信息。
	 */
	foreach(fc_l, fc_stmt->items)
	{
		CreateOpClassItem *fc_item = lfirst_node(CreateOpClassItem, fc_l);
		Oid			fc_operOid;
		Oid			fc_funcOid;
		Oid			fc_sortfamilyOid;
		OpFamilyMember *fc_member;

		switch (fc_item->itemtype)
		{
			case OPCLASS_ITEM_OPERATOR:
				if (fc_item->number <= 0 || fc_item->number > fc_maxOpNumber)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("invalid operator number %d,"
									" must be between 1 and %d",
									fc_item->number, fc_maxOpNumber)));
				if (fc_item->name->objargs != NIL)
					fc_operOid = LookupOperWithArgs(fc_item->name, false);
				else
				{
					/* 默认对输入数据类型使用二元操作 */
					fc_operOid = LookupOperName(NULL, fc_item->name->objname,
											 fc_typeoid, fc_typeoid,
											 false, -1);
				}

				if (fc_item->order_family)
					fc_sortfamilyOid = get_opfamily_oid(BTREE_AM_OID,
													 fc_item->order_family,
													 false);
				else
					fc_sortfamilyOid = InvalidOid;

#ifdef NOT_USED
				/* XXX 鉴于上面的超级用户检查，这个是多余的 */
				/* 调用者必须拥有操作符及其底层函数 */
				if (!pg_oper_ownercheck(fc_operOid, GetUserId()))
					aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR,
								   get_opname(fc_operOid));
				fc_funcOid = get_opcode(fc_operOid);
				if (!pg_proc_ownercheck(fc_funcOid, GetUserId()))
					aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
								   get_func_name(fc_funcOid));
#endif

				/* 保存信息 */
				fc_member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember));
				fc_member->is_func = false;
				fc_member->object = fc_operOid;
				fc_member->number = fc_item->number;
				fc_member->sortfamily = fc_sortfamilyOid;
				fc_assignOperTypes(fc_member, fc_amoid, fc_typeoid);
				fc_addFamilyMember(&fc_operators, fc_member);
				break;
			case OPCLASS_ITEM_FUNCTION:
				if (fc_item->number <= 0 || fc_item->number > fc_maxProcNumber)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("invalid function number %d,"
									" must be between 1 and %d",
									fc_item->number, fc_maxProcNumber)));
				fc_funcOid = LookupFuncWithArgs(OBJECT_FUNCTION, fc_item->name, false);
#ifdef NOT_USED
				/* XXX 鉴于上面的超级用户检查，这个是多余的 */
				/* 调用者必须拥有函数 */
				if (!pg_proc_ownercheck(fc_funcOid, GetUserId()))
					aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
								   get_func_name(fc_funcOid));
#endif
				/* 保存信息 */
				fc_member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember));
				fc_member->is_func = true;
				fc_member->object = fc_funcOid;
				fc_member->number = fc_item->number;

				/* 允许覆盖函数的实际参数类型 */
				if (fc_item->class_args)
					fc_processTypesSpec(fc_item->class_args,
									 &fc_member->lefttype, &fc_member->righttype);

				fc_assignProcTypes(fc_member, fc_amoid, fc_typeoid, fc_optsProcNumber);
				fc_addFamilyMember(&fc_procedures, fc_member);
				break;
			case OPCLASS_ITEM_STORAGETYPE:
				if (OidIsValid(fc_storageoid))
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("storage type specified more than once")));
				fc_storageoid = typenameTypeId(NULL, fc_item->storedtype);

#ifdef NOT_USED
				/* XXX 鉴于上面的超级用户检查，这个是多余的 */
				/* 检查我们是否拥有数据类型的所有权 */
				if (!pg_type_ownercheck(fc_storageoid, GetUserId()))
					aclcheck_error_type(ACLCHECK_NOT_OWNER, fc_storageoid);
#endif
				break;
			default:
				elog(ERROR, "unrecognized item type: %d", fc_item->itemtype);
				break;
		}
	}

	/*
	 * 如果指定了存储类型，请确保它是合法的。
	 */
	if (OidIsValid(fc_storageoid))
	{
		/* 如果与列数据类型相同，则只需删除规范 */
		if (fc_storageoid == fc_typeoid)
			fc_storageoid = InvalidOid;
		else if (!fc_amstorage)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("storage type cannot be different from data type for access method \"%s\"",
							fc_stmt->amname)));
	}

	fc_rel = table_open(OperatorClassRelationId, RowExclusiveLock);

	/*
	 * 确保没有同名的现有操作类（这只是为了提供比“重复键”更友好的错误消息）。
	 */
	if (SearchSysCacheExists3(CLAAMNAMENSP,
							  ObjectIdGetDatum(fc_amoid),
							  CStringGetDatum(fc_opcname),
							  ObjectIdGetDatum(fc_namespaceoid)))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("operator class \"%s\" for access method \"%s\" already exists",
						fc_opcname, fc_stmt->amname)));

	/*
	 * 如果我们正在创建默认操作类，请检查是否已经存在一个。
	 * （请注意，我们不将此测试限制于可见操作类；这确保了 typcache.c 可以找到其问题的唯一解决方案。）
	 */
	if (fc_stmt->isDefault)
	{
		ScanKeyData fc_skey[1];
		SysScanDesc fc_scan;

		ScanKeyInit(&fc_skey[0],
					Anum_pg_opclass_opcmethod,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_amoid));

		fc_scan = systable_beginscan(fc_rel, OpclassAmNameNspIndexId, true,
								  NULL, 1, fc_skey);

		while (HeapTupleIsValid(fc_tup = systable_getnext(fc_scan)))
		{
			Form_pg_opclass fc_opclass = (Form_pg_opclass) GETSTRUCT(fc_tup);

			if (fc_opclass->opcintype == fc_typeoid && fc_opclass->opcdefault)
				ereport(ERROR,
						(errcode(ERRCODE_DUPLICATE_OBJECT),
						 errmsg("could not make operator class \"%s\" be default for type %s",
								fc_opcname,
								TypeNameToString(fc_stmt->datatype)),
						 errdetail("Operator class \"%s\" already is the default.",
								   NameStr(fc_opclass->opcname))));
		}

		systable_endscan(fc_scan);
	}

	/*
	 * 好吧，让我们创建 pg_opclass 条目。
	 */
	memset(fc_values, 0, sizeof(fc_values));
	memset(fc_nulls, false, sizeof(fc_nulls));

	fc_opclassoid = GetNewOidWithIndex(fc_rel, OpclassOidIndexId,
									Anum_pg_opclass_oid);
	fc_values[Anum_pg_opclass_oid - 1] = ObjectIdGetDatum(fc_opclassoid);
	fc_values[Anum_pg_opclass_opcmethod - 1] = ObjectIdGetDatum(fc_amoid);
	namestrcpy(&fc_opcName, fc_opcname);
	fc_values[Anum_pg_opclass_opcname - 1] = NameGetDatum(&fc_opcName);
	fc_values[Anum_pg_opclass_opcnamespace - 1] = ObjectIdGetDatum(fc_namespaceoid);
	fc_values[Anum_pg_opclass_opcowner - 1] = ObjectIdGetDatum(GetUserId());
	fc_values[Anum_pg_opclass_opcfamily - 1] = ObjectIdGetDatum(fc_opfamilyoid);
	fc_values[Anum_pg_opclass_opcintype - 1] = ObjectIdGetDatum(fc_typeoid);
	fc_values[Anum_pg_opclass_opcdefault - 1] = BoolGetDatum(fc_stmt->isDefault);
	fc_values[Anum_pg_opclass_opckeytype - 1] = ObjectIdGetDatum(fc_storageoid);

	fc_tup = heap_form_tuple(fc_rel->rd_att, fc_values, fc_nulls);

	CatalogTupleInsert(fc_rel, fc_tup);

	heap_freetuple(fc_tup);

	/*
	 * 现在我们有了 opclass OID，为 pg_amop 和 pg_amproc 条目设置默认依赖信息。
	 * 历史上，CREATE OPERATOR CLASS 创建了对 opclass 的硬依赖，
	 * 所以我们也按照这个使用。
	 */
	foreach(fc_l, fc_operators)
	{
		OpFamilyMember *fc_op = (OpFamilyMember *) lfirst(fc_l);

		fc_op->ref_is_hard = true;
		fc_op->ref_is_family = false;
		fc_op->refobjid = fc_opclassoid;
	}
	foreach(fc_l, fc_procedures)
	{
		OpFamilyMember *fc_proc = (OpFamilyMember *) lfirst(fc_l);

		fc_proc->ref_is_hard = true;
		fc_proc->ref_is_family = false;
		fc_proc->refobjid = fc_opclassoid;
	}

	/*
	 * 让索引 AM 对依赖选择进行编辑。如果需要，它也可以对运算符和函数进行进一步验证。
	 */
	if (fc_amroutine->amadjustmembers)
		fc_amroutine->amadjustmembers(fc_opfamilyoid,
								   fc_opclassoid,
								   fc_operators,
								   fc_procedures);

	/*
	 * 现在向 pg_amop 和 pg_amproc 添加元组，绑定运算符和函数。
	 * 对它们的依赖关系也会被插入。
	 */
	fc_storeOperators(fc_stmt->opfamilyname, fc_amoid, fc_opfamilyoid,
				   fc_operators, false);
	fc_storeProcedures(fc_stmt->opfamilyname, fc_amoid, fc_opfamilyoid,
					fc_procedures, false);

	/* 让事件触发器知道发生了什么 */
	EventTriggerCollectCreateOpClass(fc_stmt, fc_opclassoid, fc_operators, fc_procedures);

	/*
	 * 为 opclass 本身创建依赖关系。注意：我们不需要与 AM 之间的依赖链接，
	 * 因为那通过 opfamily 存在。
	 */
	fc_myself.classId = OperatorClassRelationId;
	fc_myself.objectId = fc_opclassoid;
	fc_myself.objectSubId = 0;

	/* 对命名空间的依赖 */
	fc_referenced.classId = NamespaceRelationId;
	fc_referenced.objectId = fc_namespaceoid;
	fc_referenced.objectSubId = 0;
	recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_NORMAL);

	/* 对 opfamily 的依赖 */
	fc_referenced.classId = OperatorFamilyRelationId;
	fc_referenced.objectId = fc_opfamilyoid;
	fc_referenced.objectSubId = 0;
	recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_AUTO);

	/* 对索引数据类型的依赖 */
	fc_referenced.classId = TypeRelationId;
	fc_referenced.objectId = fc_typeoid;
	fc_referenced.objectSubId = 0;
	recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_NORMAL);

	/* 对存储数据类型的依赖 */
	if (OidIsValid(fc_storageoid))
	{
		fc_referenced.classId = TypeRelationId;
		fc_referenced.objectId = fc_storageoid;
		fc_referenced.objectSubId = 0;
		recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_NORMAL);
	}

	/* 对所有者的依赖 */
	recordDependencyOnOwner(OperatorClassRelationId, fc_opclassoid, GetUserId());

	/* 对扩展的依赖 */
	recordDependencyOnCurrentExtension(&fc_myself, false);

	/* 新运算符类的创建后钩子 */
	InvokeObjectPostCreateHook(OperatorClassRelationId, fc_opclassoid, 0);

	table_close(fc_rel, RowExclusiveLock);

	return fc_myself;
}


/*
 * DefineOpFamily
 *		定义一个新的索引运算符族。
 */
ObjectAddress DefineOpFamily(CreateOpFamilyStmt *fc_stmt)
{
	char	   *fc_opfname;		/* 我们正在创建的 opfamily 的名称 */
	Oid			fc_amoid,			/* 我们的 AM 的 oid */
				fc_namespaceoid;	/* 创建 opfamily 的命名空间 */
	AclResult	fc_aclresult;

	/* 将名称列表转换为一个名称和命名空间 */
	fc_namespaceoid = QualifiedNameGetCreationNamespace(fc_stmt->opfamilyname,
													 &fc_opfname);

	/* 检查我们在目标命名空间中是否有创建权限 */
	fc_aclresult = pg_namespace_aclcheck(fc_namespaceoid, GetUserId(), ACL_CREATE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
					   get_namespace_name(fc_namespaceoid));

	/* 获取访问方法 OID，如果不存在则抛出错误。 */
	fc_amoid = get_index_am_oid(fc_stmt->amname, false);

	/* XXX 我们是否应该对 AM 进行任何特权检查？ */

	/*
	 * 目前，我们要求超级用户权限才能创建一个 opfamily。请参阅
	 * DefineOpClass 中的评论。
	 */
	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to create an operator family")));

	/* 插入 pg_opfamily 目录条目 */
	return fc_CreateOpFamily(fc_stmt, fc_opfname, fc_namespaceoid, fc_amoid);
}


/*
 * AlterOpFamily
 *		在现有运算符族中添加或移除运算符/过程。
 *
 * 注意：此仅实现 ALTER OPERATOR FAMILY ... ADD/DROP。
 * 存在一些其他命令称为 ALTER OPERATOR FAMILY，
 * 但通过不同的代码路径进行。
 */
Oid AlterOpFamily(AlterOpFamilyStmt *fc_stmt)
{
	Oid			fc_amoid,			/* 我们的 AM 的 oid */
				fc_opfamilyoid;	/* opfamily 的 oid */
	int			fc_maxOpNumber,	/* amstrategies 值 */
				fc_optsProcNumber, /* amopclassopts 值 */
				fc_maxProcNumber;	/* amsupport 值 */
	HeapTuple	fc_tup;
	Form_pg_am	fc_amform;
	IndexAmRoutine *fc_amroutine;

	/* 获取关于访问方法的必要信息 */
	fc_tup = SearchSysCache1(AMNAME, CStringGetDatum(fc_stmt->amname));
	if (!HeapTupleIsValid(fc_tup))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("access method \"%s\" does not exist",
						fc_stmt->amname)));

	fc_amform = (Form_pg_am) GETSTRUCT(fc_tup);
	fc_amoid = fc_amform->oid;
	fc_amroutine = GetIndexAmRoutineByAmId(fc_amoid, false);
	ReleaseSysCache(fc_tup);

	fc_maxOpNumber = fc_amroutine->amstrategies;
	/* 如果 amstrategies 为零，仅确保操作数适合 int16 */
	if (fc_maxOpNumber <= 0)
		fc_maxOpNumber = SHRT_MAX;
	fc_maxProcNumber = fc_amroutine->amsupport;
	fc_optsProcNumber = fc_amroutine->amoptsprocnum;

	/* XXX 我们是否应该对 AM 进行任何特权检查？ */

	/* 查找 opfamily */
	fc_opfamilyoid = get_opfamily_oid(fc_amoid, fc_stmt->opfamilyname, false);

	/*
	 * 目前，我们要求超级用户权限才能更改 opfamily。
	 *
	 * XXX 如果你删除此测试，请重新启用下面的 NOT_USED 代码部分。
	 */
	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to alter an operator family")));

	/*
	 * ADD 和 DROP 情况需要从这里开始的单独代码。
	 */
	if (fc_stmt->isDrop)
		fc_AlterOpFamilyDrop(fc_stmt, fc_amoid, fc_opfamilyoid,
						  fc_maxOpNumber, fc_maxProcNumber, fc_stmt->items);
	else
		fc_AlterOpFamilyAdd(fc_stmt, fc_amoid, fc_opfamilyoid,
						 fc_maxOpNumber, fc_maxProcNumber, fc_optsProcNumber,
						 fc_stmt->items);

	return fc_opfamilyoid;
}

/*
 * ALTER OP FAMILY 的 ADD 部分
 */
static void fc_AlterOpFamilyAdd(AlterOpFamilyStmt *fc_stmt, Oid fc_amoid, Oid fc_opfamilyoid,
				 int fc_maxOpNumber, int fc_maxProcNumber, int fc_optsProcNumber,
				 List *fc_items)
{
	IndexAmRoutine *fc_amroutine = GetIndexAmRoutineByAmId(fc_amoid, false);
	List	   *fc_operators;		/* 操作符的 OpFamilyMember 列表 */
	List	   *fc_procedures;		/* 支持过程的 OpFamilyMember 列表 */
	ListCell   *fc_l;

	fc_operators = NIL;
	fc_procedures = NIL;

	/*
	 * 扫描 "items" 列表以获取更多信息。
	 */
	foreach(fc_l, fc_items)
	{
		CreateOpClassItem *fc_item = lfirst_node(CreateOpClassItem, fc_l);
		Oid			fc_operOid;
		Oid			fc_funcOid;
		Oid			fc_sortfamilyOid;
		OpFamilyMember *fc_member;

		switch (fc_item->itemtype)
		{
			case OPCLASS_ITEM_OPERATOR:
				if (fc_item->number <= 0 || fc_item->number > fc_maxOpNumber)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("invalid operator number %d,"
									" must be between 1 and %d",
									fc_item->number, fc_maxOpNumber)));
				if (fc_item->name->objargs != NIL)
					fc_operOid = LookupOperWithArgs(fc_item->name, false);
				else
				{
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("operator argument types must be specified in ALTER OPERATOR FAMILY")));
					fc_operOid = InvalidOid;	/* 保持编译器安静 */
				}

				if (fc_item->order_family)
					fc_sortfamilyOid = get_opfamily_oid(BTREE_AM_OID,
													 fc_item->order_family,
													 false);
				else
					fc_sortfamilyOid = InvalidOid;

#ifdef NOT_USED
				/* XXX 鉴于上面的超级用户检查，这个是多余的 */
				/* 调用者必须拥有操作符及其底层函数 */
				if (!pg_oper_ownercheck(fc_operOid, GetUserId()))
					aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR,
								   get_opname(fc_operOid));
				fc_funcOid = get_opcode(fc_operOid);
				if (!pg_proc_ownercheck(fc_funcOid, GetUserId()))
					aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
								   get_func_name(fc_funcOid));
#endif

				/* 保存信息 */
				fc_member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember));
				fc_member->is_func = false;
				fc_member->object = fc_operOid;
				fc_member->number = fc_item->number;
				fc_member->sortfamily = fc_sortfamilyOid;
				/* 我们可以立即设置依赖字段 */
				/* 历史上，ALTER ADD 创建了软依赖 */
				fc_member->ref_is_hard = false;
				fc_member->ref_is_family = true;
				fc_member->refobjid = fc_opfamilyoid;
				fc_assignOperTypes(fc_member, fc_amoid, InvalidOid);
				fc_addFamilyMember(&fc_operators, fc_member);
				break;
			case OPCLASS_ITEM_FUNCTION:
				if (fc_item->number <= 0 || fc_item->number > fc_maxProcNumber)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("invalid function number %d,"
									" must be between 1 and %d",
									fc_item->number, fc_maxProcNumber)));
				fc_funcOid = LookupFuncWithArgs(OBJECT_FUNCTION, fc_item->name, false);
#ifdef NOT_USED
				/* XXX 鉴于上面的超级用户检查，这个是多余的 */
				/* 调用者必须拥有函数 */
				if (!pg_proc_ownercheck(fc_funcOid, GetUserId()))
					aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
								   get_func_name(fc_funcOid));
#endif

				/* 保存信息 */
				fc_member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember));
				fc_member->is_func = true;
				fc_member->object = fc_funcOid;
				fc_member->number = fc_item->number;
				/* 我们可以立即设置依赖字段 */
				/* 历史上，ALTER ADD 创建了软依赖 */
				fc_member->ref_is_hard = false;
				fc_member->ref_is_family = true;
				fc_member->refobjid = fc_opfamilyoid;

				/* 允许覆盖函数的实际参数类型 */
				if (fc_item->class_args)
					fc_processTypesSpec(fc_item->class_args,
									 &fc_member->lefttype, &fc_member->righttype);

				fc_assignProcTypes(fc_member, fc_amoid, InvalidOid, fc_optsProcNumber);
				fc_addFamilyMember(&fc_procedures, fc_member);
				break;
			case OPCLASS_ITEM_STORAGETYPE:
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("STORAGE cannot be specified in ALTER OPERATOR FAMILY")));
				break;
			default:
				elog(ERROR, "unrecognized item type: %d", fc_item->itemtype);
				break;
		}
	}

	/*
	 * 让索引 AM 对依赖选择进行编辑。如果需要，它也可以对运算符和函数进行进一步验证。
	 */
	if (fc_amroutine->amadjustmembers)
		fc_amroutine->amadjustmembers(fc_opfamilyoid,
								   InvalidOid,	/* 无特定的操作类 */
								   fc_operators,
								   fc_procedures);

	/*
	 * 向 pg_amop 和 pg_amproc 添加元组，将操作符和
	 * 函数绑定在一起。对它们的依赖也会被插入。
	 */
	fc_storeOperators(fc_stmt->opfamilyname, fc_amoid, fc_opfamilyoid,
				   fc_operators, true);
	fc_storeProcedures(fc_stmt->opfamilyname, fc_amoid, fc_opfamilyoid,
					fc_procedures, true);

	/* 使信息可供事件触发器使用 */
	EventTriggerCollectAlterOpFam(fc_stmt, fc_opfamilyoid,
								  fc_operators, fc_procedures);
}

/*
 * ALTER OP FAMILY 的部分 DROP
 */
static void fc_AlterOpFamilyDrop(AlterOpFamilyStmt *fc_stmt, Oid fc_amoid, Oid fc_opfamilyoid,
				  int fc_maxOpNumber, int fc_maxProcNumber, List *fc_items)
{
	List	   *fc_operators;		/* 操作符的 OpFamilyMember 列表 */
	List	   *fc_procedures;		/* 支持过程的 OpFamilyMember 列表 */
	ListCell   *fc_l;

	fc_operators = NIL;
	fc_procedures = NIL;

	/*
	 * 扫描 "items" 列表以获取更多信息。
	 */
	foreach(fc_l, fc_items)
	{
		CreateOpClassItem *fc_item = lfirst_node(CreateOpClassItem, fc_l);
		Oid			fc_lefttype,
					fc_righttype;
		OpFamilyMember *fc_member;

		switch (fc_item->itemtype)
		{
			case OPCLASS_ITEM_OPERATOR:
				if (fc_item->number <= 0 || fc_item->number > fc_maxOpNumber)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("invalid operator number %d,"
									" must be between 1 and %d",
									fc_item->number, fc_maxOpNumber)));
				fc_processTypesSpec(fc_item->class_args, &fc_lefttype, &fc_righttype);
				/* 保存信息 */
				fc_member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember));
				fc_member->is_func = false;
				fc_member->number = fc_item->number;
				fc_member->lefttype = fc_lefttype;
				fc_member->righttype = fc_righttype;
				fc_addFamilyMember(&fc_operators, fc_member);
				break;
			case OPCLASS_ITEM_FUNCTION:
				if (fc_item->number <= 0 || fc_item->number > fc_maxProcNumber)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("invalid function number %d,"
									" must be between 1 and %d",
									fc_item->number, fc_maxProcNumber)));
				fc_processTypesSpec(fc_item->class_args, &fc_lefttype, &fc_righttype);
				/* 保存信息 */
				fc_member = (OpFamilyMember *) palloc0(sizeof(OpFamilyMember));
				fc_member->is_func = true;
				fc_member->number = fc_item->number;
				fc_member->lefttype = fc_lefttype;
				fc_member->righttype = fc_righttype;
				fc_addFamilyMember(&fc_procedures, fc_member);
				break;
			case OPCLASS_ITEM_STORAGETYPE:
				/* 语法防止此出现 */
			default:
				elog(ERROR, "unrecognized item type: %d", fc_item->itemtype);
				break;
		}
	}

	/*
	 * 从 pg_amop 和 pg_amproc 中删除元组。
	 */
	fc_dropOperators(fc_stmt->opfamilyname, fc_amoid, fc_opfamilyoid, fc_operators);
	fc_dropProcedures(fc_stmt->opfamilyname, fc_amoid, fc_opfamilyoid, fc_procedures);

	/* 使信息可供事件触发器使用 */
	EventTriggerCollectAlterOpFam(fc_stmt, fc_opfamilyoid,
								  fc_operators, fc_procedures);
}


/*
 * 处理在 ALTER ADD/DROP 中使用的显式参数类型
 */
static void fc_processTypesSpec(List *fc_args, Oid *fc_lefttype, Oid *fc_righttype)
{
	TypeName   *fc_typeName;

	Assert(fc_args != NIL);

	fc_typeName = (TypeName *) linitial(fc_args);
	*fc_lefttype = typenameTypeId(NULL, fc_typeName);

	if (list_length(fc_args) > 1)
	{
		fc_typeName = (TypeName *) lsecond(fc_args);
		*fc_righttype = typenameTypeId(NULL, fc_typeName);
	}
	else
		*fc_righttype = *fc_lefttype;

	if (list_length(fc_args) > 2)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("one or two argument types must be specified")));
}


/*
 * 确定要分配给操作符的 lefttype/righttype，
 * 并进行我们能管理的任何有效性检查。
 */
static void fc_assignOperTypes(OpFamilyMember *fc_member, Oid fc_amoid, Oid fc_typeoid)
{
	Operator	fc_optup;
	Form_pg_operator fc_opform;

	/* 获取操作符定义 */
	fc_optup = SearchSysCache1(OPEROID, ObjectIdGetDatum(fc_member->object));
	if (!HeapTupleIsValid(fc_optup))
		elog(ERROR, "cache lookup failed for operator %u", fc_member->object);
	fc_opform = (Form_pg_operator) GETSTRUCT(fc_optup);

	/*
	 * 操作族操作符必须是二元的。
	 */
	if (fc_opform->oprkind != 'b')
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("index operators must be binary")));

	if (OidIsValid(fc_member->sortfamily))
	{
		/*
		 * 排序操作，检查索引是否支持这一点。（我们或许还可以
		 * 检查操作符返回的类型是否被排序族支持，
		 * 但这在这里似乎不值得麻烦。如果不支持，
		 * 该操作符将永远无法与任何 ORDER BY 子句匹配，但不会
		 * 导致更糟糕的后果。此外，尝试检查这会
		 * 在转储/重新加载期间造成排序危险：可能
		 * 家族已经创建但尚未填充所需的
		 * 操作符。）
		 */
		IndexAmRoutine *fc_amroutine = GetIndexAmRoutineByAmId(fc_amoid, false);

		if (!fc_amroutine->amcanorderbyop)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("access method \"%s\" does not support ordering operators",
							get_am_name(fc_amoid))));
	}
	else
	{
		/*
		 * 搜索操作符必须返回布尔值。
		 */
		if (fc_opform->oprresult != BOOLOID)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("index search operators must return boolean")));
	}

	/*
	 * 如果未指定 lefttype/righttype，使用操作符的输入类型
	 */
	if (!OidIsValid(fc_member->lefttype))
		fc_member->lefttype = fc_opform->oprleft;
	if (!OidIsValid(fc_member->righttype))
		fc_member->righttype = fc_opform->oprright;

	ReleaseSysCache(fc_optup);
}

/*
 * 确定要分配给支持过程的 lefttype/righttype，
 * 并进行我们能管理的任何有效性检查。
 */
static void fc_assignProcTypes(OpFamilyMember *fc_member, Oid fc_amoid, Oid fc_typeoid,
				int fc_opclassOptsProcNum)
{
	HeapTuple	fc_proctup;
	Form_pg_proc fc_procform;

	/* 获取过程定义 */
	fc_proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_member->object));
	if (!HeapTupleIsValid(fc_proctup))
		elog(ERROR, "cache lookup failed for function %u", fc_member->object);
	fc_procform = (Form_pg_proc) GETSTRUCT(fc_proctup);

	/* 检查操作类选项解析函数的签名 */
	if (fc_member->number == fc_opclassOptsProcNum)
	{
		if (OidIsValid(fc_typeoid))
		{
			if ((OidIsValid(fc_member->lefttype) && fc_member->lefttype != fc_typeoid) ||
				(OidIsValid(fc_member->righttype) && fc_member->righttype != fc_typeoid))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("associated data types for operator class options parsing functions must match opclass input type")));
		}
		else
		{
			if (fc_member->lefttype != fc_member->righttype)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("left and right associated data types for operator class options parsing functions must match")));
		}

		if (fc_procform->prorettype != VOIDOID ||
			fc_procform->pronargs != 1 ||
			fc_procform->proargtypes.values[0] != INTERNALOID)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("invalid operator class options parsing function"),
					 errhint("Valid signature of operator class options parsing function is %s.",
							 "(internal) RETURNS void")));
	}

	/*
	 * btree 比较过程必须是返回 int4 的 2-参数过程。btree
	 * 排序支持过程必须接收内部参数并返回 void。btree 的 in_range
	 * 过程必须是返回 bool 的 5-参数过程。btree 的 equalimage 过程必须
	 * 接收 1 个参数并返回 bool。hash 支持过程 1 必须是返回 int4 的 1-参数过程，
	 * 而过程 2 必须是返回 int8 的 2-参数过程。
	 * 否则我们无法得知。
	 */
	else if (fc_amoid == BTREE_AM_OID)
	{
		if (fc_member->number == BTORDER_PROC)
		{
			if (fc_procform->pronargs != 2)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("btree comparison functions must have two arguments")));
			if (fc_procform->prorettype != INT4OID)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("btree comparison functions must return integer")));

			/*
			 * 如果未指定 lefttype/righttype，使用过程的输入
			 * 类型
			 */
			if (!OidIsValid(fc_member->lefttype))
				fc_member->lefttype = fc_procform->proargtypes.values[0];
			if (!OidIsValid(fc_member->righttype))
				fc_member->righttype = fc_procform->proargtypes.values[1];
		}
		else if (fc_member->number == BTSORTSUPPORT_PROC)
		{
			if (fc_procform->pronargs != 1 ||
				fc_procform->proargtypes.values[0] != INTERNALOID)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("btree sort support functions must accept type \"internal\"")));
			if (fc_procform->prorettype != VOIDOID)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("btree sort support functions must return void")));

			/*
			 * 无法从过程推断 lefttype/righttype，所以使用默认规则
			 */
		}
		else if (fc_member->number == BTINRANGE_PROC)
		{
			if (fc_procform->pronargs != 5)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("btree in_range functions must have five arguments")));
			if (fc_procform->prorettype != BOOLOID)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("btree in_range functions must return boolean")));

			/*
			 * 如果未指定 lefttype/righttype，使用过程的输入
			 * 类型（我们查看测试值和偏移量参数）
			 */
			if (!OidIsValid(fc_member->lefttype))
				fc_member->lefttype = fc_procform->proargtypes.values[0];
			if (!OidIsValid(fc_member->righttype))
				fc_member->righttype = fc_procform->proargtypes.values[2];
		}
		else if (fc_member->number == BTEQUALIMAGE_PROC)
		{
			if (fc_procform->pronargs != 1)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("btree equal image functions must have one argument")));
			if (fc_procform->prorettype != BOOLOID)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("btree equal image functions must return boolean")));

			/*
			 * pg_amproc 函数通过 (lefttype, righttype) 索引，但是
			 * equalimage 函数只能在 CREATE INDEX 时调用。
			 * 左类型和右类型总是使用相同的 opclass opcintype OID。
			 * 提供跨类型的例程是没有意义的。
			 * 在这里拒绝跨类型的 ALTER OPERATOR FAMILY ... ADD FUNCTION 4
			 * 语句。
			 */
			if (fc_member->lefttype != fc_member->righttype)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("btree equal image functions must not be cross-type")));
		}
	}
	else if (fc_amoid == HASH_AM_OID)
	{
		if (fc_member->number == HASHSTANDARD_PROC)
		{
			if (fc_procform->pronargs != 1)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("hash function 1 must have one argument")));
			if (fc_procform->prorettype != INT4OID)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("hash function 1 must return integer")));
		}
		else if (fc_member->number == HASHEXTENDED_PROC)
		{
			if (fc_procform->pronargs != 2)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("hash function 2 must have two arguments")));
			if (fc_procform->prorettype != INT8OID)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("hash function 2 must return bigint")));
		}

		/*
		 * 如果未指定 lefttype/righttype，使用 proc 的输入类型
		 */
		if (!OidIsValid(fc_member->lefttype))
			fc_member->lefttype = fc_procform->proargtypes.values[0];
		if (!OidIsValid(fc_member->righttype))
			fc_member->righttype = fc_procform->proargtypes.values[0];
	}

	/*
	 * 在 CREATE OPERATOR CLASS 中的默认值是使用类的 opcintype 作为
	 * lefttype 和 righttype。在 CREATE 或 ALTER OPERATOR FAMILY 中，opcintype
	 * 不可用，因此让用户指定类型。
	 */
	if (!OidIsValid(fc_member->lefttype))
		fc_member->lefttype = fc_typeoid;
	if (!OidIsValid(fc_member->righttype))
		fc_member->righttype = fc_typeoid;

	if (!OidIsValid(fc_member->lefttype) || !OidIsValid(fc_member->righttype))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("associated data types must be specified for index support function")));

	ReleaseSysCache(fc_proctup);
}

/*
 * 在适当的列表中添加一个新的家族成员，检查是否有重复的策略或 proc 编号。
 */
static void fc_addFamilyMember(List **fc_list, OpFamilyMember *fc_member)
{
	ListCell   *fc_l;

	foreach(fc_l, *fc_list)
	{
		OpFamilyMember *fc_old = (OpFamilyMember *) lfirst(fc_l);

		if (fc_old->number == fc_member->number &&
			fc_old->lefttype == fc_member->lefttype &&
			fc_old->righttype == fc_member->righttype)
		{
			if (fc_member->is_func)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("function number %d for (%s,%s) appears more than once",
								fc_member->number,
								format_type_be(fc_member->lefttype),
								format_type_be(fc_member->righttype))));
			else
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("operator number %d for (%s,%s) appears more than once",
								fc_member->number,
								format_type_be(fc_member->lefttype),
								format_type_be(fc_member->righttype))));
		}
	}
	*fc_list = lappend(*fc_list, fc_member);
}

/*
 * 将操作符转储到 pg_amop
 *
 * 我们还会在 pg_depend 中为 pg_amop 条目创建依赖关系条目。
 */
static void fc_storeOperators(List *fc_opfamilyname, Oid fc_amoid, Oid fc_opfamilyoid,
			   List *fc_operators, bool fc_isAdd)
{
	Relation	fc_rel;
	Datum		fc_values[Natts_pg_amop];
	bool		fc_nulls[Natts_pg_amop];
	HeapTuple	fc_tup;
	Oid			fc_entryoid;
	ObjectAddress fc_myself,
				fc_referenced;
	ListCell   *fc_l;

	fc_rel = table_open(AccessMethodOperatorRelationId, RowExclusiveLock);

	foreach(fc_l, fc_operators)
	{
		OpFamilyMember *fc_op = (OpFamilyMember *) lfirst(fc_l);
		char		fc_oppurpose;

		/*
		 * 如果添加到现有家族，检查与现有 pg_amop 条目是否有冲突
		 * （只是为了给出更好的错误信息）
		 */
		if (fc_isAdd &&
			SearchSysCacheExists4(AMOPSTRATEGY,
								  ObjectIdGetDatum(fc_opfamilyoid),
								  ObjectIdGetDatum(fc_op->lefttype),
								  ObjectIdGetDatum(fc_op->righttype),
								  Int16GetDatum(fc_op->number)))
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("operator %d(%s,%s) already exists in operator family \"%s\"",
							fc_op->number,
							format_type_be(fc_op->lefttype),
							format_type_be(fc_op->righttype),
							NameListToString(fc_opfamilyname))));

		fc_oppurpose = OidIsValid(fc_op->sortfamily) ? AMOP_ORDER : AMOP_SEARCH;

		/* 创建 pg_amop 条目 */
		memset(fc_values, 0, sizeof(fc_values));
		memset(fc_nulls, false, sizeof(fc_nulls));

		fc_entryoid = GetNewOidWithIndex(fc_rel, AccessMethodOperatorOidIndexId,
									  Anum_pg_amop_oid);
		fc_values[Anum_pg_amop_oid - 1] = ObjectIdGetDatum(fc_entryoid);
		fc_values[Anum_pg_amop_amopfamily - 1] = ObjectIdGetDatum(fc_opfamilyoid);
		fc_values[Anum_pg_amop_amoplefttype - 1] = ObjectIdGetDatum(fc_op->lefttype);
		fc_values[Anum_pg_amop_amoprighttype - 1] = ObjectIdGetDatum(fc_op->righttype);
		fc_values[Anum_pg_amop_amopstrategy - 1] = Int16GetDatum(fc_op->number);
		fc_values[Anum_pg_amop_amoppurpose - 1] = CharGetDatum(fc_oppurpose);
		fc_values[Anum_pg_amop_amopopr - 1] = ObjectIdGetDatum(fc_op->object);
		fc_values[Anum_pg_amop_amopmethod - 1] = ObjectIdGetDatum(fc_amoid);
		fc_values[Anum_pg_amop_amopsortfamily - 1] = ObjectIdGetDatum(fc_op->sortfamily);

		fc_tup = heap_form_tuple(fc_rel->rd_att, fc_values, fc_nulls);

		CatalogTupleInsert(fc_rel, fc_tup);

		heap_freetuple(fc_tup);

		/* 创建它的依赖关系 */
		fc_myself.classId = AccessMethodOperatorRelationId;
		fc_myself.objectId = fc_entryoid;
		fc_myself.objectSubId = 0;

		fc_referenced.classId = OperatorRelationId;
		fc_referenced.objectId = fc_op->object;
		fc_referenced.objectSubId = 0;

		/* 请参见 amapi.h 中关于依赖强度的注释 */
		recordDependencyOn(&fc_myself, &fc_referenced,
						   fc_op->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO);

		fc_referenced.classId = fc_op->ref_is_family ? OperatorFamilyRelationId :
			OperatorClassRelationId;
		fc_referenced.objectId = fc_op->refobjid;
		fc_referenced.objectSubId = 0;

		recordDependencyOn(&fc_myself, &fc_referenced,
						   fc_op->ref_is_hard ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);

		if (fc_typeDepNeeded(fc_op->lefttype, fc_op))
		{
			fc_referenced.classId = TypeRelationId;
			fc_referenced.objectId = fc_op->lefttype;
			fc_referenced.objectSubId = 0;

			/* 请参见 amapi.h 中关于依赖强度的注释 */
			recordDependencyOn(&fc_myself, &fc_referenced,
							   fc_op->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO);
		}

		if (fc_op->lefttype != fc_op->righttype &&
			fc_typeDepNeeded(fc_op->righttype, fc_op))
		{
			fc_referenced.classId = TypeRelationId;
			fc_referenced.objectId = fc_op->righttype;
			fc_referenced.objectSubId = 0;

			/* 请参见 amapi.h 中关于依赖强度的注释 */
			recordDependencyOn(&fc_myself, &fc_referenced,
							   fc_op->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO);
		}

		/* 搜索操作符还需要对引用的 opfamily 的依赖 */
		if (OidIsValid(fc_op->sortfamily))
		{
			fc_referenced.classId = OperatorFamilyRelationId;
			fc_referenced.objectId = fc_op->sortfamily;
			fc_referenced.objectSubId = 0;

			recordDependencyOn(&fc_myself, &fc_referenced,
							   fc_op->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO);
		}

		/* 此访问方法操作符的创建后钩子 */
		InvokeObjectPostCreateHook(AccessMethodOperatorRelationId,
								   fc_entryoid, 0);
	}

	table_close(fc_rel, RowExclusiveLock);
}

/*
 * 将过程（支持例程）转储到 pg_amproc
 *
 * 我们还会在 pg_depend 中为 pg_amproc 条目创建依赖关系条目。
 */
static void fc_storeProcedures(List *fc_opfamilyname, Oid fc_amoid, Oid fc_opfamilyoid,
				List *fc_procedures, bool fc_isAdd)
{
	Relation	fc_rel;
	Datum		fc_values[Natts_pg_amproc];
	bool		fc_nulls[Natts_pg_amproc];
	HeapTuple	fc_tup;
	Oid			fc_entryoid;
	ObjectAddress fc_myself,
				fc_referenced;
	ListCell   *fc_l;

	fc_rel = table_open(AccessMethodProcedureRelationId, RowExclusiveLock);

	foreach(fc_l, fc_procedures)
	{
		OpFamilyMember *fc_proc = (OpFamilyMember *) lfirst(fc_l);

		/*
		 * 如果添加到现有家族，检查与现有 pg_amproc 条目是否有冲突
		 * （只是为了给出更好的错误信息）
		 */
		if (fc_isAdd &&
			SearchSysCacheExists4(AMPROCNUM,
								  ObjectIdGetDatum(fc_opfamilyoid),
								  ObjectIdGetDatum(fc_proc->lefttype),
								  ObjectIdGetDatum(fc_proc->righttype),
								  Int16GetDatum(fc_proc->number)))
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("function %d(%s,%s) already exists in operator family \"%s\"",
							fc_proc->number,
							format_type_be(fc_proc->lefttype),
							format_type_be(fc_proc->righttype),
							NameListToString(fc_opfamilyname))));

		/* 创建 pg_amproc 条目 */
		memset(fc_values, 0, sizeof(fc_values));
		memset(fc_nulls, false, sizeof(fc_nulls));

		fc_entryoid = GetNewOidWithIndex(fc_rel, AccessMethodProcedureOidIndexId,
									  Anum_pg_amproc_oid);
		fc_values[Anum_pg_amproc_oid - 1] = ObjectIdGetDatum(fc_entryoid);
		fc_values[Anum_pg_amproc_amprocfamily - 1] = ObjectIdGetDatum(fc_opfamilyoid);
		fc_values[Anum_pg_amproc_amproclefttype - 1] = ObjectIdGetDatum(fc_proc->lefttype);
		fc_values[Anum_pg_amproc_amprocrighttype - 1] = ObjectIdGetDatum(fc_proc->righttype);
		fc_values[Anum_pg_amproc_amprocnum - 1] = Int16GetDatum(fc_proc->number);
		fc_values[Anum_pg_amproc_amproc - 1] = ObjectIdGetDatum(fc_proc->object);

		fc_tup = heap_form_tuple(fc_rel->rd_att, fc_values, fc_nulls);

		CatalogTupleInsert(fc_rel, fc_tup);

		heap_freetuple(fc_tup);

		/* 创建它的依赖关系 */
		fc_myself.classId = AccessMethodProcedureRelationId;
		fc_myself.objectId = fc_entryoid;
		fc_myself.objectSubId = 0;

		fc_referenced.classId = ProcedureRelationId;
		fc_referenced.objectId = fc_proc->object;
		fc_referenced.objectSubId = 0;

		/* 请参见 amapi.h 中关于依赖强度的注释 */
		recordDependencyOn(&fc_myself, &fc_referenced,
						   fc_proc->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO);

		fc_referenced.classId = fc_proc->ref_is_family ? OperatorFamilyRelationId :
			OperatorClassRelationId;
		fc_referenced.objectId = fc_proc->refobjid;
		fc_referenced.objectSubId = 0;

		recordDependencyOn(&fc_myself, &fc_referenced,
						   fc_proc->ref_is_hard ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);

		if (fc_typeDepNeeded(fc_proc->lefttype, fc_proc))
		{
			fc_referenced.classId = TypeRelationId;
			fc_referenced.objectId = fc_proc->lefttype;
			fc_referenced.objectSubId = 0;

			/* 请参见 amapi.h 中关于依赖强度的注释 */
			recordDependencyOn(&fc_myself, &fc_referenced,
							   fc_proc->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO);
		}

		if (fc_proc->lefttype != fc_proc->righttype &&
			fc_typeDepNeeded(fc_proc->righttype, fc_proc))
		{
			fc_referenced.classId = TypeRelationId;
			fc_referenced.objectId = fc_proc->righttype;
			fc_referenced.objectSubId = 0;

			/* 请参见 amapi.h 中关于依赖强度的注释 */
			recordDependencyOn(&fc_myself, &fc_referenced,
							   fc_proc->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO);
		}

		/* 访问方法过程的创建后钩子 */
		InvokeObjectPostCreateHook(AccessMethodProcedureRelationId,
								   fc_entryoid, 0);
	}

	table_close(fc_rel, RowExclusiveLock);
}

/*
 * 检测 pg_amop 或 pg_amproc 条目是否需要对其 lefttype 或 righttype 进行显式依赖
 *
 * 除非条目通过其引用的操作符或函数具有间接依赖，否则我们会建立这样的依赖。
 * 这对于操作符几乎总是正确的，但对于支持函数可能并不总是如此。
 */
static bool fc_typeDepNeeded(Oid fc_typid, OpFamilyMember *fc_member)
{
	bool		fc_result = true;

	/*
	 * 如果类型是固定的，则不需要依赖。这可能有点违反分层原则
	 * （recordDependencyOn 无论如何会忽略请求），但是这是一个便宜的测试
	 * 并且通常会在这里节省 syscache 查找的开销。
	 */
	if (IsPinnedObject(TypeRelationId, fc_typid))
		return false;

	/* 不，这样检查函数或操作符的输入类型。 */
	if (fc_member->is_func)
	{
		Oid		   *fc_argtypes;
		int			fc_nargs;

		(void) get_func_signature(fc_member->object, &fc_argtypes, &fc_nargs);
		for (int fc_i = 0; fc_i < fc_nargs; fc_i++)
		{
			if (fc_typid == fc_argtypes[fc_i])
			{
				fc_result = false; /* 匹配，不需要依赖 */
				break;
			}
		}
		pfree(fc_argtypes);
	}
	else
	{
		Oid			fc_lefttype,
					fc_righttype;

		op_input_types(fc_member->object, &fc_lefttype, &fc_righttype);
		if (fc_typid == fc_lefttype || fc_typid == fc_righttype)
			fc_result = false;		/* 匹配，不需要依赖 */
	}
	return fc_result;
}


/*
 * 从操作族中移除操作符条目。
 *
 * 注意：这仅允许对操作族的“松散”成员，因此行为始终为 RESTRICT。
 */
static void fc_dropOperators(List *fc_opfamilyname, Oid fc_amoid, Oid fc_opfamilyoid,
			  List *fc_operators)
{
	ListCell   *fc_l;

	foreach(fc_l, fc_operators)
	{
		OpFamilyMember *fc_op = (OpFamilyMember *) lfirst(fc_l);
		Oid			fc_amopid;
		ObjectAddress fc_object;

		fc_amopid = GetSysCacheOid4(AMOPSTRATEGY, Anum_pg_amop_oid,
								 ObjectIdGetDatum(fc_opfamilyoid),
								 ObjectIdGetDatum(fc_op->lefttype),
								 ObjectIdGetDatum(fc_op->righttype),
								 Int16GetDatum(fc_op->number));
		if (!OidIsValid(fc_amopid))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("operator %d(%s,%s) does not exist in operator family \"%s\"",
							fc_op->number,
							format_type_be(fc_op->lefttype),
							format_type_be(fc_op->righttype),
							NameListToString(fc_opfamilyname))));

		fc_object.classId = AccessMethodOperatorRelationId;
		fc_object.objectId = fc_amopid;
		fc_object.objectSubId = 0;

		performDeletion(&fc_object, DROP_RESTRICT, 0);
	}
}

/*
 * 从操作族中移除过程条目。
 *
 * 注意：这仅允许对于操作族的“松散”成员，因此
 * 行为始终是RESTRICT。
 */
static void fc_dropProcedures(List *fc_opfamilyname, Oid fc_amoid, Oid fc_opfamilyoid,
			   List *fc_procedures)
{
	ListCell   *fc_l;

	foreach(fc_l, fc_procedures)
	{
		OpFamilyMember *fc_op = (OpFamilyMember *) lfirst(fc_l);
		Oid			fc_amprocid;
		ObjectAddress fc_object;

		fc_amprocid = GetSysCacheOid4(AMPROCNUM, Anum_pg_amproc_oid,
								   ObjectIdGetDatum(fc_opfamilyoid),
								   ObjectIdGetDatum(fc_op->lefttype),
								   ObjectIdGetDatum(fc_op->righttype),
								   Int16GetDatum(fc_op->number));
		if (!OidIsValid(fc_amprocid))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("function %d(%s,%s) does not exist in operator family \"%s\"",
							fc_op->number,
							format_type_be(fc_op->lefttype),
							format_type_be(fc_op->righttype),
							NameListToString(fc_opfamilyname))));

		fc_object.classId = AccessMethodProcedureRelationId;
		fc_object.objectId = fc_amprocid;
		fc_object.objectSubId = 0;

		performDeletion(&fc_object, DROP_RESTRICT, 0);
	}
}

/*
 * ALTER OPERATOR CLASS 设置模式/重命名的子例程
 *
 * 在给定命名空间中是否已经存在具有给定名称和签名的操作类？
 * 如果是，则引发适当的错误消息。
 */
void IsThereOpClassInNamespace(const char *fc_opcname, Oid fc_opcmethod,
						  Oid fc_opcnamespace)
{
	/* 确保新名称不存在 */
	if (SearchSysCacheExists3(CLAAMNAMENSP,
							  ObjectIdGetDatum(fc_opcmethod),
							  CStringGetDatum(fc_opcname),
							  ObjectIdGetDatum(fc_opcnamespace)))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("operator class \"%s\" for access method \"%s\" already exists in schema \"%s\"",
						fc_opcname,
						get_am_name(fc_opcmethod),
						get_namespace_name(fc_opcnamespace))));
}

/*
 * ALTER OPERATOR FAMILY 设置模式/重命名的子例程
 *
 * 在给定命名空间中是否已经存在具有给定名称和签名的操作族？
 * 如果是，则引发适当的错误消息。
 */
void IsThereOpFamilyInNamespace(const char *fc_opfname, Oid fc_opfmethod,
						   Oid fc_opfnamespace)
{
	/* 确保新名称不存在 */
	if (SearchSysCacheExists3(OPFAMILYAMNAMENSP,
							  ObjectIdGetDatum(fc_opfmethod),
							  CStringGetDatum(fc_opfname),
							  ObjectIdGetDatum(fc_opfnamespace)))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("operator family \"%s\" for access method \"%s\" already exists in schema \"%s\"",
						fc_opfname,
						get_am_name(fc_opfmethod),
						get_namespace_name(fc_opfnamespace))));
}
